import * as yaml from 'js-yaml';
import { Behaviour } from "./engine/Behaviour";
import { Rectangle } from './engine/math';
import { ResourceManager } from "./engine/ResourceManager";
import { GameLifeCycleSystem } from './engine/systems/GameLifeCycleSystem';
import { MouseControlSystem } from './engine/systems/MouseControlSystem';
import { CanvasContextRenderingSystem } from './engine/systems/RenderingSystem';
import { System } from './engine/systems/System';
import { TransformSystem } from './engine/systems/TransformSystem';
import { Transform } from "./engine/Transform";
import { GameSystem } from './GameSystem';
import { Host } from './host';

const canvas = document.getElementById('game') as HTMLCanvasElement;
const context = canvas.getContext('2d')
context.font = "40px Arial";

const gameObjects: { [id: string]: GameObject } = {

}




export class Matrix {

    a = 1;
    b = 0;
    c = 0;
    d = 1;
    tx = 0;
    ty = 0;

    constructor(a: number = 1, b: number = 0, c: number = 0, d: number = 1, tx: number = 0, ty: number = 0) {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
        this.tx = tx;
        this.ty = ty;

    }

    updateFromTransformProperties(x: number, y: number, scaleX: number, scaleY: number, rotation: number) {
        this.tx = x;
        this.ty = y;

        let skewX, skewY;
        skewX = skewY = rotation / 180 * Math.PI;


        this.a = Math.cos(skewY) * scaleX;
        this.b = Math.sin(skewY) * scaleX;
        this.c = -Math.sin(skewX) * scaleY;
        this.d = Math.cos(skewX) * scaleY;

    }
}

export class GameEngine {
    defaultSceneName: string = './assets/scenes/main.yaml'
    rootGameObject = new GameObject()
    lastTime: number = 0;
    storeDuringTime: number = 0;
    resourceManager = new ResourceManager();
    systems: System[] = [];
    start() {
        const host = new Host();

        this.addSystem(new GameLifeCycleSystem());
        this.addSystem(new TransformSystem());
        this.addSystem(new GameSystem(context));
        this.addSystem(new CanvasContextRenderingSystem(context));
        this.addSystem(new MouseControlSystem())
        this.resourceManager.loadText(this.defaultSceneName, () => {
            this.startup();
            host.start().then(() => {
                const text = this.serilize(this.rootGameObject.children[0]);
                host.send({ command: 'save', data: text })
            })

        })
    }


    addSystem(system: System) {
        this.systems.push(system);
        system.rootGameObject = this.rootGameObject;
    }



    removeSystem(system: System) {
        const index = this.systems.indexOf(system);
        if (index >= 0) {
            this.systems.splice(index);
        }
    }

    private startup() {
        this.rootGameObject.addBehaviour(new Transform());
        const text = this.resourceManager.get(this.defaultSceneName);
        const scene = this.unserilize(text)
        if (scene) {
            this.rootGameObject.addChild(scene);
        }
        for (const system of this.systems) {
            system.onStart();
        }
        this.enterFrame(0);
    }

    //反序列化，接收一个文本返回一个gameobject
    private unserilize(text: string): GameObject {
        try {
            console.log(text)
            let data = yaml.load(text);
            return createGameObject(data);
        }
        catch (e) {
            alert('配置文件解析失败')
        }
        return null;
    }
    //与unserilize对称写
    private serilize(gameObject: GameObject): string {
        const json = extractGameObject(gameObject);
        const text = yaml.dump(json, {
            noCompatMode: true
        });
        console.log(text);
        return text;
    }


    enterFrame(advancedTime: number) {
        let duringTime = advancedTime - this.lastTime + this.storeDuringTime;
        const milesecondPerFrame = 1000 / 60;
        while (duringTime > milesecondPerFrame) {
            for (const system of this.systems) {
                system.onTick(milesecondPerFrame);
            }
            duringTime -= milesecondPerFrame;
        }
        this.storeDuringTime = duringTime
        context.setTransform(1, 0, 0, 1, 0, 0)
        context.clearRect(0, 0, canvas.width, canvas.height)
        for (const system of this.systems) {
            system.onUpdate();
        }
        requestAnimationFrame(this.enterFrame.bind(this));
        this.lastTime = advancedTime;
    }

}

export interface Renderer {
    getBounds(): Rectangle
}

export class GameObject {

    id: string;

    parent: GameObject;

    onClick?: Function;

    behaviours: Behaviour[] = [];

    renderer: Renderer;

    children: GameObject[] = [];

    addChild(child: GameObject) {
        this.children.push(child);
        child.parent = this;
    }

    removeChild(child: GameObject) {
        const index = this.children.indexOf(child);
        if (index >= 0) {
            this.children.splice(index);
        }
    }

    addBehaviour(behaviour: Behaviour) {
        this.behaviours.push(behaviour);
        behaviour.gameObject = this;
    }

    //泛型
    getBehaviour<T extends typeof Behaviour>(clz: T): InstanceType<T> {
        for (const behaviour of this.behaviours) {
            if (behaviour.constructor.name === clz.name) {
                return behaviour as any;
            }
        }
        return null;
    }
}

const behaviourTable = {

}

type GameObjectData = {
    id?: string
    behaviours: BehaviourData[]
    children?: GameObjectData[]
}

type BehaviourData = {
    type: string,
    properties?: { [index: string]: any }
}


export function registerBehaviourClass(behaviourClass: any) {
    const className = behaviourClass.name;
    behaviourTable[className] = behaviourClass;
}

export function extractGameObject(gameObject: GameObject): GameObjectData {
    const gameObjectData: GameObjectData = {
        behaviours: [],
    };
    if (gameObject.id) {
        gameObjectData.id = gameObject.id;
    }
    for (const behaviour of gameObject.behaviours) {
        const behaviourClass = (behaviour as any).__proto__
        const behaviourClassName = (behaviour as any).constructor.name;
        const __metadatas = behaviourClass.__metadatas || [];
        const behaviourData: BehaviourData = { type: behaviourClassName }
        gameObjectData.behaviours.push(behaviourData)
        for (const metadata of __metadatas) {
            behaviourData.properties = behaviourData.properties || {};
            behaviourData.properties[metadata.key] = behaviour[metadata.key];
        }
    }
    for (const child of gameObject.children) {
        const childData = extractGameObject(child);
        gameObjectData.children = gameObjectData.children || [];
        gameObjectData.children.push(childData);
    }
    console.log(gameObjectData)
    return gameObjectData
}


export function createGameObject(data: any) {
    const gameObject = new GameObject();
    if (data.id) {
        gameObjects[data.id] = gameObject;
    }
    //反序列化那些属性是根据metadata（decoration）来决定的
    //同样，序列化那些属性，也应该根据同样的metadata来确定
    for (const behaviourData of data.behaviours) {
        const behaviourClass = behaviourTable[behaviourData.type];
        if (!behaviourClass) {
            throw new Error('传入的类名不对:' + behaviourData.type);
        }
        const behaviour: Behaviour = new behaviourClass();
        const __metadatas = behaviourClass.prototype.__metadatas || [];
        for (const metadata of __metadatas) {
            const key = metadata.key;
            const value = behaviourData.properties[key]
            metadata.validator(value);
            behaviour[key] = value
        }
        gameObject.addBehaviour(behaviour);
    }

    if (data.children) {
        for (const childData of data.children) {
            const child = createGameObject(childData);
            gameObject.addChild(child);
        }
    }

    return gameObject;
}



export function getGameObjectById(id: string) {
    return gameObjects[id]
}